package cn.dansj.common.utils.io;

import cn.dansj.common.utils.transfer.ArrayUtils;
import cn.dansj.common.utils.transfer.StringUtils;
import cn.dansj.common.utils.transfer.Transformation;
import cn.dansj.common.utils.transfer.Verification;

import java.io.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.OpenOption;
import java.nio.file.Paths;
import java.util.BitSet;
import java.util.List;

@SuppressWarnings("all")
public abstract class FileUtils {
    @SuppressWarnings("ResultOfMethodCallIgnored")
    public static void copyFile(InputStream rs, String destPath, String suffix) throws IOException {
        File newFile = new File(destPath);
        //判断文件的后缀是否满足要求,不满足的不进行复制,防止中间有其他文件生成导致复制过程异常
        if (Verification.checkNotNull(suffix)) {
            String[] destSplit = StringUtils.split(destPath, ".");
            if (!ArrayUtils.asList(StringUtils.split(suffix, ",")).contains(destSplit[destSplit.length - 1])) {
                return;
            }
        }

        if (!newFile.exists()) {
            newFile.getParentFile().mkdirs();
            newFile.createNewFile();
        }

        BufferedInputStream bis = new BufferedInputStream(rs);
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(newFile));
        byte[] bys = new byte[1024];
        int len;
        while ((len = bis.read(bys)) != -1) {
            bos.write(bys, 0, len);
        }
        rs.close();
        bos.close();
        bis.close();
    }

    public static boolean exists(String path) {
        return new File(path).exists();
    }

    public static boolean isFile(String path) {
        return new File(path).isFile();
    }

    public static boolean isDirectory(String path) {
        return new File(path).isDirectory();
    }

    public static File file(String path) {
        return new File(Transformation.nvl(path, ""));
    }

    public static boolean deleteDirectory(File root) {
        if (root != null && root.exists()) {
            if (root.isDirectory()) {
                File[] children = root.listFiles();
                if (children != null) {
                    for (File child : children) {
                        deleteDirectory(child);
                    }
                }
            }
            return root.delete();
        }
        return false;
    }

    public static String readFile(String file, Charset charset) throws IOException {
        StringBuilder content = new StringBuilder();

        try (InputStream inputStream = new FileInputStream(file);
             BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, charset))) {
            String line;
            while ((line = reader.readLine()) != null) {
                content.append(line).append("\n");
            }
        }

        if (content.length() > 0) content.deleteCharAt(content.lastIndexOf("\n"));
        return content.toString();
    }

    public static String readFile(String file) throws IOException {
        return readFile(file, Charset.defaultCharset());
    }

    public static void writeToFile(String file, String content, OpenOption... options) throws IOException {
        try (BufferedWriter bw = Files.newBufferedWriter(Paths.get(file), StandardCharsets.UTF_8, options)) {
            bw.write(content);
        }
    }

    public static Charset getFileEncode(String file, boolean ignoreBom) {
        return Charset.forName(EncodeUtil.getEncode(file, ignoreBom));
    }

    public static Charset getFileEncode(String file) {
        return Charset.forName(EncodeUtil.getEncode(file, true));
    }

    public static Boolean isUtf8(File file) {
        boolean isUtf8 = true;
        byte[] buffer = readByteArrayData(file);
        int end = buffer.length;
        for (int i = 0; i < end; i++) {
            byte temp = buffer[i];
            if ((temp & 0x80) == 0) {// 0xxxxxxx
                continue;
            } else if ((temp & 0xC0) == 0xC0 && (temp & 0x20) == 0) {// 110xxxxx 10xxxxxx
                if (i + 1 < end && (buffer[i + 1] & 0x80) == 0x80 && (buffer[i + 1] & 0x40) == 0) {
                    i = i + 1;
                    continue;
                }
            } else if ((temp & 0xE0) == 0xE0 && (temp & 0x10) == 0) {// 1110xxxx 10xxxxxx 10xxxxxx
                if (i + 2 < end && (buffer[i + 1] & 0x80) == 0x80 && (buffer[i + 1] & 0x40) == 0
                        && (buffer[i + 2] & 0x80) == 0x80 && (buffer[i + 2] & 0x40) == 0) {
                    i = i + 2;
                    continue;
                }
            } else if ((temp & 0xF0) == 0xF0 && (temp & 0x08) == 0) {// 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
                if (i + 3 < end && (buffer[i + 1] & 0x80) == 0x80 && (buffer[i + 1] & 0x40) == 0
                        && (buffer[i + 2] & 0x80) == 0x80 && (buffer[i + 2] & 0x40) == 0
                        && (buffer[i + 3] & 0x80) == 0x80 && (buffer[i + 3] & 0x40) == 0) {
                    i = i + 3;
                    continue;
                }
            }
            isUtf8 = false;
            break;
        }
        return isUtf8;
    }

    public static Boolean isGbk(File file) {
        boolean isGbk = true;
        byte[] buffer = readByteArrayData(file);
        int end = buffer.length;
        for (int i = 0; i < end; i++) {
            byte temp = buffer[i];
            if ((temp & 0x80) == 0) {
                continue;// B0A1-F7FE//A1A1-A9FE
            } else if ((Byte.toUnsignedInt(temp) < 0xAA && Byte.toUnsignedInt(temp) > 0xA0)
                    || (Byte.toUnsignedInt(temp) < 0xF8 && Byte.toUnsignedInt(temp) > 0xAF)) {
                if (i + 1 < end) {
                    if (Byte.toUnsignedInt(buffer[i + 1]) < 0xFF && Byte.toUnsignedInt(buffer[i + 1]) > 0xA0
                            && Byte.toUnsignedInt(buffer[i + 1]) != 0x7F) {
                        i = i + 1;
                        continue;
                    }
                } // 8140-A0FE
            } else if (Byte.toUnsignedInt(temp) < 0xA1 && Byte.toUnsignedInt(temp) > 0x80) {
                if (i + 1 < end) {
                    if (Byte.toUnsignedInt(buffer[i + 1]) < 0xFF && Byte.toUnsignedInt(buffer[i + 1]) > 0x3F
                            && Byte.toUnsignedInt(buffer[i + 1]) != 0x7F) {
                        i = i + 1;
                        continue;
                    }
                } // AA40-FEA0//A840-A9A0
            } else if ((Byte.toUnsignedInt(temp) < 0xFF && Byte.toUnsignedInt(temp) > 0xA9)
                    || (Byte.toUnsignedInt(temp) < 0xAA && Byte.toUnsignedInt(temp) > 0xA7)) {
                if (i + 1 < end) {
                    if (Byte.toUnsignedInt(buffer[i + 1]) < 0xA1 && Byte.toUnsignedInt(buffer[i + 1]) > 0x3F
                            && Byte.toUnsignedInt(buffer[i + 1]) != 0x7F) {
                        i = i + 1;
                        continue;
                    }
                }
            }
            isGbk = false;
            break;
        }
        return isGbk;
    }

    private static byte[] readByteArrayData(File file) {
        byte[] readByte = null;
        try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
             ByteArrayOutputStream output = new ByteArrayOutputStream()) {

            byte[] byt = new byte[1024 * 4];
            int len;
            while ((len = bis.read(byt)) != -1) {
                if (len < 1024 * 4) {
                    output.write(byt, 0, len);
                } else
                    output.write(byt);
            }
            readByte = output.toByteArray();
        } catch (IOException ignored) {
        }
        return readByte;
    }

    public static List<String> readLines(File file, Charset charset) throws IOException {
        return Files.readAllLines(file.toPath(), charset);
    }

    public static List<String> readLines(File file, String charsetName) throws IOException {
        return readLines(file, charsetName == null ? Charset.defaultCharset() : Charset.forName(charsetName));
    }

    private static class EncodeUtil {
        private static final int BYTE_SIZE = 8;
        public static String CODE_UTF8 = "UTF-8";
        public static String CODE_UTF8_BOM = "UTF-8_BOM";
        public static String CODE_GBK = "GBK";

        /**
         * 通过文件全名称获取编码集名称
         */
        public static String getEncode(String fullFileName, boolean ignoreBom) {
            try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(fullFileName))) {
                return getEncode(bis, ignoreBom);
            } catch (Exception exception) {
                return "";
            }
        }

        /**
         * 通过文件缓存流获取编码集名称，文件流必须为未曾读取过的
         */
        private static String getEncode(BufferedInputStream bis, boolean ignoreBom) throws Exception {
            bis.mark(0);

            String encodeType = "";
            byte[] head = new byte[3];
            bis.read(head);
            if (head[0] == -1 && head[1] == -2) {
                encodeType = "UTF-16";
            } else if (head[0] == -2 && head[1] == -1) {
                encodeType = "Unicode";
            } else if (head[0] == -17 && head[1] == -69 && head[2] == -65) { //带BOM
                if (ignoreBom) {
                    encodeType = CODE_UTF8;
                } else {
                    encodeType = CODE_UTF8_BOM;
                }
            } else if ("Unicode".equals(encodeType)) {
                encodeType = "UTF-16";
            } else if (isUTF8(bis)) {
                encodeType = CODE_UTF8;
            } else {
                encodeType = CODE_GBK;
            }
            return encodeType;
        }

        /**
         * 是否是无BOM的UTF8格式，不判断常规场景，只区分无BOM UTF8和GBK
         */
        private static boolean isUTF8(BufferedInputStream bis) throws Exception {
            bis.reset();

            //读取第一个字节
            int code = bis.read();
            do {
                BitSet bitSet = convert2BitSet(code);
                //判断是否为单字节
                if (bitSet.get(0)) {//多字节时，再读取N个字节
                    if (!checkMultiByte(bis, bitSet)) {//未检测通过,直接返回
                        return false;
                    }
                }  //单字节时什么都不用做，再次读取字节

                code = bis.read();
            } while (code != -1);
            return true;
        }

        /**
         * 检测多字节，判断是否为utf8，已经读取了一个字节
         */
        private static boolean checkMultiByte(BufferedInputStream bis, BitSet bitSet) throws Exception {
            int count = getCountOfSequential(bitSet);
            byte[] bytes = new byte[count - 1];//已经读取了一个字节，不能再读取
            bis.read(bytes);
            for (byte b : bytes) {
                if (!checkUtf8Byte(b)) {
                    return false;
                }
            }
            return true;
        }

        /**
         * 检测单字节，判断是否为utf8
         */
        private static boolean checkUtf8Byte(byte b) throws Exception {
            BitSet bitSet = convert2BitSet(b);
            return bitSet.get(0) && !bitSet.get(1);
        }

        /**
         * 检测bitSet中从开始有多少个连续的1
         */
        private static int getCountOfSequential(BitSet bitSet) {
            int count = 0;
            for (int i = 0; i < BYTE_SIZE; i++) {
                if (bitSet.get(i)) {
                    count++;
                } else {
                    break;
                }
            }
            return count;
        }

        /**
         * 将整形转为BitSet
         */
        private static BitSet convert2BitSet(int code) {
            BitSet bitSet = new BitSet(BYTE_SIZE);

            for (int i = 0; i < BYTE_SIZE; i++) {
                int tmp3 = code >> (BYTE_SIZE - i - 1);
                int tmp2 = 0x1 & tmp3;
                if (tmp2 == 1) {
                    bitSet.set(i);
                }
            }
            return bitSet;
        }

        /**
         * 将一指定编码的文件转换为另一编码的文件
         */
        public static void convert(String oldFullFileName, String oldCharsetName, String newFullFileName, String newCharsetName) throws Exception {
            StringBuilder content = new StringBuilder();

            BufferedReader bin = new BufferedReader(new InputStreamReader(new FileInputStream(oldFullFileName), oldCharsetName));
            String line;
            while ((line = bin.readLine()) != null) {
                content.append(line);
                content.append(System.lineSeparator());
            }
            newFullFileName = newFullFileName.replace("\\", "/");
            File dir = new File(newFullFileName.substring(0, newFullFileName.lastIndexOf("/")));
            if (!dir.exists()) {
                dir.mkdirs();
            }
            Writer out = new OutputStreamWriter(new FileOutputStream(newFullFileName), newCharsetName);
            out.write(content.toString());
        }
    }

    public static String convertStreamToString(InputStream inputStream) throws IOException {
        ByteArrayOutputStream result = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int length;
        while ((length = inputStream.read(buffer)) != -1) {
            result.write(buffer, 0, length);
        }
        return result.toString(StandardCharsets.UTF_8.name());
    }
}